﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using System.Collections;

using LeanEngine.Validation;
using LeanEngine.Extensions;

namespace LeanEngine.Validation.Windows.Forms
{
    /// <summary>
    /// Provides a mechanism to specify automatic or user requested validation for controls.
    /// </summary>
    /// <remarks>
    /// This component is an extender provider that adds properties to controls that allow 
    /// the designer of the application to execute the validation associated with the property type.
    /// <para/>
    /// Validation can be automatic through the <see cref="Control.Validating"/> event, or initiated 
    /// via code by invoking the <see cref="PropertyValidatorProvider.PerformValidation(Control)"/> method; in 
    /// both cases the control needs to have been properly configured through the validation provider.
    /// <para/>
    /// An instance of <see cref="ErrorProvider"/> can be specified for the component; in this case
    /// the validation errors resulting from validating a control are set on the error provider as
    /// a properly formatted error message for the control.
    /// <para/>
    /// A validation provider can be enabled or disabled through the <see cref="PropertyValidatorProvider.Enabled"/>
    /// property. When it is disabled no validation will occur and the validaton provider will be considered
    /// valid, and any error messages posted to the <see cref="ErrorProvider"/> will be cleared. 
    /// If re-enabled, it will continue to be valid and no error messages will be posted until validation
    /// is triggered again.
    /// </remarks>
    /// <seealso cref="PropertyValidatorProvider.IsValid"/>
    /// <seealso cref="PropertyValidatorProvider.Enabled"/>
    /// <seealso cref="PropertyValidatorProvider.PerformValidation(Control)"/>    
    [ToolboxItemFilter("System.Windows.Forms")]
    [ProvideProperty("PerformValidation", typeof(Control))]
    [ProvideProperty("SourcePropertyName", typeof(Control))]
    [ProvideProperty("ValidatedProperty", typeof(Control))]
    [ProvideProperty("DisplayErrorIconControl", typeof(Control))]
    public sealed class PropertyValidatorProvider : Component, IExtenderProvider
    {
        #region Declaration Members

        private bool _enabled;
        private Dictionary<Control, ValidatedProperty> _items;

        #endregion

        #region Constructor Members

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyValidatorProvider"/> class.
        /// </summary>
        public PropertyValidatorProvider()
        {
            _enabled = true;
            _items = new Dictionary<Control, ValidatedProperty>();
        }

        #endregion

        #region PropertyInfo Members

        /// <summary>
        /// The indication of whether the <see cref="PropertyValidatorProvider"/> will perform validations.
        /// </summary>
        /// <remarks>
        /// No validation will be performed if the <see cref="PropertyValidatorProvider"/> is not enabled, even
        /// when requested through the <see cref="PropertyValidatorProvider.PerformValidation(Control)"/> method.
        /// <para/>
        /// All existing errors will be cleared if set to <see langword="false"/>, but will not be restated
        /// if set back to <see langword="true"/>.
        /// </remarks>
        [DefaultValue(true)]
        [Description("DescriptionEnabled")]
        [Category("Validation")]
        public bool Enabled
        {
            get { return _enabled; }
            set
            {
                _enabled = value;
                if (_enabled == false)
                {
                    // clear status and validation errors
                    foreach (var validatedItem in _items.Values)
                    {
                        ClearValidation(validatedItem);
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="ErrorProvider"/> to which formatted validation errors should be posted
        /// when validating a <see cref="Control"/>.
        /// </summary>
        [Description("Gets or sets the error provider to which formatted validation errors should be posted")]
        [Category("Validation")]
        public ErrorProvider ErrorProvider { get; set; }        

        /// <summary>
        /// Gets or sets the name for the type to use as the source of specifications for the validations
        /// performed by the <see cref="PropertyValidatorProvider"/>.
        /// </summary>
        [TypeConverter(typeof(RequiredStringConverter))]
        [Description("DescriptionSourceType")]
        [Category("Validation")]
        public string SourceTypeName { get; set; }

        /// <summary>
        /// Gets an indication of whether the values for the controls managed by the <see cref="PropertyValidatorProvider"/> have
        /// been successfully validated.
        /// </summary>
        /// <value>
        /// <see langword="true"/> when the values for all the managed controls have been successfully validated; 
        /// <see langword="false"/> otherwise.
        /// </value>
        /// <remarks>
        /// This property reflects the outcome of validations that were performed, so if there is a managed control with an invalid
        /// value for which validation has not been performed the value for this property might be <see langword="true"/>.
        /// </remarks>
        [Browsable(false)]
        public bool IsValid
        {
            get
            {
                foreach (var control in _items.Values)
                {
                    if (!control.IsValid) return false;
                }

                return true;
            }
        }

        /// <summary>
        /// Gets all errors messages from the validators.
        /// </summary>
        [Browsable(false)]
        public string ErrorMessages
        {
            get
            {
                var errors = new StringBuilder();
                foreach(var key in this._items.Keys)
                {
                    if (!this._items[key].IsValid && this._items[key].PerformValidation)
                    {
                        if (this._items[key].ErrorMessage.IsNullOrEmpty()) InitializeErrorMessage(key);

                        errors.Append("- ");
                        errors.Append(this._items[key].ErrorMessage);
                        errors.Append(Environment.NewLine);

                        DisplayError(this._items[key]);
                    }
                }

                try
                {
                    return errors.ToString();
                }
                finally
                {
                    errors.Remove(0, errors.Length);
                }
            }
        }

        #endregion

        #region IDispose Members

        /// <summary>
        /// Releases managed resources.
        /// </summary>
        /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; 
        /// <see langword="false"/> to release only unmanaged resources. </param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    foreach(var item in this._items)
                    {
                        if (this.ErrorProvider != null) this.ErrorProvider.SetError(item.Value.Control, null);
                        item.Value.Dispose();
                    }

                    _items.Clear();
                    _items = null;
                }
            }
            finally
            {
                base.Dispose(disposing);
            }
        }

        #endregion

        #region Method Members

        internal ValidatedProperty EnsurevalidatedItem(Control control)
        {
            if (control == null)
            {
                throw new ArgumentNullException("control");
            }

            if (!_items.ContainsKey(control))
            {
                _items[control] = new ValidatedProperty(this, control);                
            }

            return _items[control];
        }

        /// <summary>
        /// Gets the existing LeanEngine.Windows.Forms.ValidatedProperty from the provider.
        /// </summary>
        /// <param name="control">The control to use to get the item.</param>
        public ValidatedProperty GetValidatedItem(Control control)
        {
            return _items[control];
        }

        /// <summary>
        /// Gets the existing LeanEngine.Windows.Forms.ValidatedProperty from the provider.
        /// </summary>
        /// <param name="sourcePropertyName">The source property name to use to get the item.</param>
        public ValidatedProperty GetValidatedItem(string sourcePropertyName)
        {
            return (from i in this._items
                    where i.Value.SourcePropertyName.Equals(sourcePropertyName)
                    select i.Value).FirstOrDefault();
        }

        /// <summary>
        /// Gets the indication of whether automatic validation should be performed for 
        /// <paramref name="control"/> when it fires the <see cref="Control.Validating"/> event.
        /// </summary>
        /// <param name="control">The configured <see cref="Control"/>.</param>
        /// <returns><see langword="true"/> when the control should be automatically validated; 
        /// otherwise, <see langword="false"/>.</returns>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is null.</exception>
        [Description("Gets the indication of whether automatic validation should be performed")]
        [Category("Validation")]
        [DefaultValue(false)]
        public bool GetPerformValidation(Control control)
        {
            return EnsurevalidatedItem(control).PerformValidation;
        }

        /// <summary>
        /// Gets the name of the property on the type specified by <see cref="PropertyValidatorProvider.SourceTypeName"/> for which
        /// the validation specification should be retrieved to validate the value for <paramref name="control"/>.
        /// </summary>
        /// <param name="control">The configured <see cref="Control"/>.</param>
        /// <returns>The property name.</returns>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is null.</exception>
        [TypeConverter(typeof(RequiredStringConverter))]
        [Description("Gets the name of the property on the type specified type for which the validation should be retrieve to validate its value.")]
        [Category("Validation")]
        [DefaultValue(null)]
        public string GetSourcePropertyName(Control control)
        {
            return EnsurevalidatedItem(control).SourcePropertyName;
        }

        /// <summary>
        /// Gets the name of the property to use when extracting the value from <paramref name="control"/>.
        /// </summary>
        /// <param name="control">The configured <see cref="Control"/>.</param>
        /// <returns>The name of the property.</returns>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is null.</exception>
        [Description("Gets the name of the property to use when extracting the value.")]
        [Category("Validation")]
        [DefaultValue(ValidatedProperty.DEFAULT_VALIDATED_PROPERTY)]
        public string GetValidatedProperty(Control control)
        {
            return EnsurevalidatedItem(control).ValidatedPropertyName;
        }

        /// <summary>
        /// Gets the control which will be display the error icon.
        /// </summary>
        /// <param name="control">The <see cref="Control"/> to configure.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is null.</exception>
        [Description("Gets the name of the property to use when extracting the value.")]
        [Category("Validation")]
        [DefaultValue(null)]
        public Control GetDisplayErrorIconControl(Control control)
        {
            var validatedControl = EnsurevalidatedItem(control);
            return validatedControl.DisplayErrorIconControl;
        }

        /// <summary>
        /// Invokes the validation process for all controls
        /// </summary>
        /// <remarks>
        /// This method allows for programmatic invocation of the validation process. The control needs to be already
        /// configured for validation with the validation provider through the extender properties.
        /// </remarks>
        /// <exception cref="InvalidOperationException">when the "SourceTypeName" has not been specified or is invalid.</exception>
        /// <exception cref="InvalidOperationException">when the source property name for the control to validate has not 
        /// been specified or is invalid.</exception>
        public void PerformValidation()
        {
            if (this._items.IsNull())
            {
                throw new InvalidOperationException("The internal dictionary of controls was null.");
            }

            foreach (var value in this._items.Values)
            {
                PerformValidation(value);
            }
        }

        /// <summary>
        /// Invokes the validation process for a control.
        /// </summary>
        /// <remarks>
        /// This method allows for programmatic invocation of the validation process. The control needs to be already
        /// configured for validation with the validation provider through the extender properties.
        /// </remarks>
        /// <param name="control">The control to validate.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is not registered with the validation provider
        /// by specifying the extended properties.</exception>
        /// <exception cref="InvalidOperationException">when the <see cref="PropertyValidatorProvider.SourceTypeName"/> has not 
        /// been specified or is invalid.</exception>
        /// <exception cref="InvalidOperationException">when the source property name for the control to validate has not 
        /// been specified or is invalid.</exception>
        public void PerformValidation(Control control)
        {
            if (control == null)
            {
                throw new ArgumentNullException("control");
            }

            var existingvalidatedItem = GetValidatedItem(control);
            if (existingvalidatedItem == null)
            {
                throw new ArgumentException("Control '" + control.Name + "' not extends validation provider.", "control");
            }

            PerformValidation(existingvalidatedItem);
        }

        /// <summary>
        /// Invokes the validation process for a control.
        /// </summary>
        /// <remarks>
        /// This method allows for programmatic invocation of the validation process. The control needs to be already
        /// configured for validation with the validation provider through the extender properties.
        /// </remarks>
        /// <param name="validatedItem">The validated control to validate.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="validatedItem"/> is <see langword="null"/>.</exception>
        /// <exception cref="InvalidOperationException">when the <see cref="PropertyValidatorProvider.SourceTypeName"/> has not 
        /// been specified or is invalid.</exception>
        /// <exception cref="InvalidOperationException">when the source property name for the control to validate has not 
        /// been specified or is invalid.</exception>
        internal void PerformValidation(ValidatedProperty validatedItem)
        {
            if (_enabled)
            {
                if (validatedItem == null)
                {
                    throw new ArgumentNullException("validatedItem");
                }

                if (validatedItem.PerformValidation)
                {
                    var control = (validatedItem.DisplayErrorIconControl.IsNull() ? validatedItem.Control : validatedItem.DisplayErrorIconControl);

                    if(validatedItem.Validate())
                    {
                        this.ErrorProvider.SetError(control, null);
                    }
                    else if (this.ErrorProvider != null)
                    {
                        this.ErrorProvider.SetError(control, validatedItem.ErrorMessage);
                    }
                }
            }
        }

        /// <summary>
        /// Sets the indication of whether automatic validation should be performed for 
        /// <paramref name="control"/> when it fires the <see cref="Control.Validating"/> event.
        /// </summary>
        /// <param name="control">The <see cref="Control"/> to configure.</param>
        /// <param name="performValidation"><see langword="true"/> if the control should be automatically validated; 
        /// otherwise, <see langword="false"/>.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is null.</exception>
        public void SetPerformValidation(Control control, bool performValidation)
        {
            var validatedControl = EnsurevalidatedItem(control);
            validatedControl.PerformValidation = performValidation;

            ClearValidation(validatedControl);
        }

        /// <summary>
        /// Sets the name of the property on the type specified by <see cref="PropertyValidatorProvider.SourceTypeName"/> for which
        /// the validation specification should be retrieved to validate the value for <paramref name="control"/>.
        /// </summary>
        /// <param name="control">The <see cref="Control"/> to configure.</param>
        /// <param name="sourcePropertyName">The property name.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is null.</exception>
        public void SetSourcePropertyName(Control control, string sourcePropertyName)
        {
            var validatedControl = EnsurevalidatedItem(control);
            validatedControl.SourcePropertyName = sourcePropertyName;
            InitializeErrorMessage(control);
            ClearValidation(validatedControl);
        }

        /// <summary>
        /// Sets the name of the property to use when extracting the value from <paramref name="control"/>.
        /// </summary>
        /// <param name="control">The <see cref="Control"/> to configure.</param>
        /// <param name="validatedPropertyName">The name of the property.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is null.</exception>
        public void SetValidatedProperty(Control control, string validatedPropertyName)
        {
            var validatedControl = EnsurevalidatedItem(control);
            validatedControl.ValidatedPropertyName = validatedPropertyName;
            InitializeErrorMessage(control);
            ClearValidation(validatedControl);
        }

        /// <summary>
        /// Sets the control which will be display the error icon.
        /// </summary>
        /// <param name="control">The <see cref="Control"/> to configure.</param>
        /// <param name="controlToDisplayError">The control which will be display the error icon.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="control"/> is null.</exception>
        public void SetDisplayErrorIconControl(Control control, Control controlToDisplayError)
        {
            var validatedControl = EnsurevalidatedItem(control);
            validatedControl.DisplayErrorIconControl = controlToDisplayError;
        }

        private void DisplayError(ValidatedProperty validatedControl)
        {
            if (this.ErrorProvider != null)
            {
                var control = (validatedControl.DisplayErrorIconControl.IsNull() ? validatedControl.Control : validatedControl.DisplayErrorIconControl);
                this.ErrorProvider.SetError(control, validatedControl.ErrorMessage);
            }
        }

        private void ClearValidation(ValidatedProperty validatedControl)
        {
            if (this.ErrorProvider != null)
            {
                validatedControl.IsValid = true;
                var control = (validatedControl.DisplayErrorIconControl.IsNull() ? validatedControl.Control : validatedControl.DisplayErrorIconControl);
                this.ErrorProvider.SetError(control, null);
            }
        }

        private void InitializeErrorMessage(Control control)
        {
            var validatedControl = EnsurevalidatedItem(control);

            if (this.SourceTypeName.IsNotNullOrEmpty() &&
                validatedControl.SourcePropertyName.IsNotNullOrEmpty())
            {
                var dtoType = Type.GetType(this.SourceTypeName);
                var property = dtoType.GetProperty(validatedControl.SourcePropertyName);
                var validatorsList = ValidatorMetadataCache.GetValidatorList(property);

                if (validatorsList.Count > 0)
                {
                    validatedControl.ErrorMessage = validatorsList[0].ErrorMessage;
                }
            }
        }

        #endregion

        #region IExtenderProvider Members

        bool IExtenderProvider.CanExtend(object extendee)
        {
            return extendee is Control;
        }

        #endregion
    }
}
